﻿// (c) Copyright Damian Mehers http://damianblog.com/
// This source is subject to the Microsoft Public License.
// See http://www.microsoft.com/resources/sharedsource/licensingbasics/sharedsourcelicenses.mspx.
// All other rights reserved.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;

namespace MceFM.Last.KeyHook {
  internal class Hooker : NativeWindow {
    private const int KeyDown = 256;
    private const int LeftArrow = 37;
    private const int RightArrow = 39;
    private const int WS_CLIPCHILDREN = 0x02000000;

    private readonly HookStuff.LocalWindowsHook keyboardHook =
      new HookStuff.LocalWindowsHook(HookStuff.HookType.WH_KEYBOARD_LL);

    public event EventHandler<ArrowKeyEventArgs> ArrowKey;

    public void Start() {
      Thread thread = new Thread(ThreadEntry);
      thread.IsBackground = true;
      thread.Start();
      
      WaitUntilInitialized();
    }

    private void WaitUntilInitialized() {
      lock(initializedLock) {
        while (!initialized) {
          Monitor.Wait(initializedLock);
        }
      }
      if (initializedException != null) {
        throw initializedException;
      }
    }

    private readonly object initializedLock = new object();
    private bool initialized = false;
    private Exception initializedException;

    private void ThreadEntry() {
      try {
        CreateParams cp = new CreateParams();

        // Fill in the CreateParams details.
        cp.Caption = "MceFM RCInterceptor Hidden window";
        cp.ClassName = null;
        cp.X = 0x7FFFFFFF;
        cp.Y = 0x7FFFFFFF;
        cp.Height = 0;
        cp.Width = 0;
        cp.Style = WS_CLIPCHILDREN;

        // Create the actual invisible window
#pragma warning disable DoNotCallOverridableMethodsInConstructor
        CreateHandle(cp);
#pragma warning restore DoNotCallOverridableMethodsInConstructor

        InstallWindowsHook();
      } catch(Exception ex) {
        Trace.TraceError("Error installing windows hook", ex);
        initializedException = ex;
      } finally {
        initialized = true;
        lock(initializedLock) {
          Monitor.Pulse(initializedLock);
        }
      }
      Application.Run();
    }

    private void InstallWindowsHook() {
      IntPtr moduleHandle = Marshal.GetHINSTANCE(GetType().Module);

      keyboardHook.HookInvoked += keyboardHook_HookInvoked;
      if(!keyboardHook.Install(moduleHandle, 0)) {
        Trace.TraceError("InstallWindowsHook failed");
      } else {
        Trace.TraceInformation("Keyboard Hook installed");
      }

      Thread thread = new Thread(ArrowKeyDespatcher);
      thread.IsBackground = true;
      thread.Start();
    }

    private int keyboardHook_HookInvoked(object sender, HookStuff.HookEventArgs e) {
      int vKey = HookStuff.GetKeyPressed(e);

      if(e.wParam == KeyDown && (vKey == LeftArrow || vKey == RightArrow)) {
        lock(despatchQueue) {
          despatchQueue.Enqueue(new ArrowKeyEventArgs(vKey == LeftArrow));
          Monitor.Pulse(despatchQueue);
        }
      }
      return 0; // S_OK
    }


    private readonly Queue<ArrowKeyEventArgs> despatchQueue = new Queue<ArrowKeyEventArgs>();

    private void ArrowKeyDespatcher() {
      while(true) {
        try {
          lock(despatchQueue) {
            while(despatchQueue.Count == 0) {
              Monitor.Wait(despatchQueue);
            }
            ArrowKeyEventArgs args = despatchQueue.Dequeue();
            EventHandler<ArrowKeyEventArgs> tmp = ArrowKey;
            if (tmp != null) {
              tmp(this, args);
            }
          }
        } catch(Exception ex) {
          Trace.TraceError("Error sending ArrowKeyEvent: {0}", ex);
        }
      }
#pragma warning disable FunctionNeverReturns
    }
#pragma warning restore FunctionNeverReturns
  }
}